/**
 * Copyright 2019 吉鼎科技.

 * <p>
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package cn.easyplatform.engine;

import cn.easyplatform.cfg.EngineConfiguration;
import cn.easyplatform.contexts.Contexts;
import cn.easyplatform.contexts.EngineContext;
import cn.easyplatform.dao.utils.DaoUtils;
import cn.easyplatform.entities.EntityInfo;
import cn.easyplatform.entities.beans.ResourceBean;
import cn.easyplatform.entities.transform.TransformerFactory;
import cn.easyplatform.i18n.I18N;
import cn.easyplatform.lang.Strings;
import cn.easyplatform.lang.stream.StringOutputStream;
import cn.easyplatform.services.IProjectService;
import cn.easyplatform.services.SystemServiceId;
import cn.easyplatform.spi.engine.Engine;
import cn.easyplatform.spi.extension.ApplicationService;
import cn.easyplatform.spi.listener.ApplicationListener;
import cn.easyplatform.type.EntityType;
import cn.easyplatform.type.StateType;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.config.Ini;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.sql.*;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.*;


/**
 * @author <a href="mailto:davidchen@epclouds.com">littleDog</a> <br/>
 * @since 2.0.0 <br/>
 */
class EngineImpl implements Engine, EngineContext {

    private final static Logger log = LoggerFactory.getLogger(EngineImpl.class);

    // 系统启动时间
    private Date startTime;
    // 引擎配置接口
    private EngineConfigurationImpl engineConfiguration;

    EngineImpl(EngineConfigurationImpl ec) {
        this.engineConfiguration = ec;
    }

    @Override
    public void init(Map<String, String> args) {
        engineConfiguration.init(args);
    }

    @Override
    public boolean checkModel() {
        return engineConfiguration.checkModel();
    }

    @Override
    public void setupModel(Map<String, String> properties) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                ApplicationListener applicationListener = engineConfiguration.getApplicationListener();
                Connection conn = null;
                Statement stmt = null;
                ResultSet rs = null;
                PreparedStatement pstmt = null;
                InputStream is = null;
                Logger log = LoggerFactory.getLogger(DaoUtils.class);
                String dbUser = properties.get("username");
                String dbPass = properties.get("password");
                String dbName = properties.get("name");
                String dbUrl = String.format("jdbc:mysql://%s:%s?useUnicode=true&characterEncoding=UTF-8", properties.get("address"), properties.get("port"));
                try {
                    Class.forName("com.mysql.jdbc.Driver");
                    conn = DriverManager.getConnection(dbUrl, dbUser, dbPass);
                    applicationListener.publish(I18N.getLabel("setup.db.create", dbName), "", 10);
                    stmt = conn.createStatement();
                    stmt.execute("DROP DATABASE IF EXISTS `" + dbName + "`");
                    stmt.execute("CREATE DATABASE `" + dbName + "` CHARACTER SET 'utf8' COLLATE 'utf8_bin'");
                    conn.setCatalog(dbName);//切换到当前数据库
                    applicationListener.publish(I18N.getLabel("setup.db.import", dbName), "", 15);
                    List<String> data = IOUtils.readLines(is = new FileInputStream(System.getProperty("easyplatform.home") + "/scheme/app_metadata.sql"), "utf-8");
                    is.close();
                    stmt = conn.createStatement();
                    StringBuilder sb = new StringBuilder();
                    for (String line : data) {
                        if (!line.startsWith("--") && !Strings.isBlank(line)) {
                            sb.append(line);
                            if (line.endsWith(";")) {
                                String sql = sb.toString();
                                if (log.isDebugEnabled())
                                    log.debug(sql);
                                applicationListener.publish(null, String.format("%s", StringUtils.abbreviate(sql, 100)), 30);
                                stmt.execute(sql);
                                sb.setLength(0);
                            }
                        }
                    }
                    stmt.close();
                    pstmt = conn.prepareStatement("SELECT modelId,content FROM ep_model_info WHERE type=? AND modelId<>?");
                    pstmt.setString(1, EntityType.DATASOURCE.getName());
                    pstmt.setString(2, SystemServiceId.SYS_ENTITY_DS);
                    rs = pstmt.executeQuery();
                    if (rs.next()) {
                        EntityInfo e = new EntityInfo();
                        e.setContent(rs.getString(2));
                        e.setId(rs.getString(1));
                        e.setType(EntityType.DATASOURCE.getName());
                        ResourceBean rb = TransformerFactory.newInstance().transformFromXml(e);
                        rb.getProps().put("username", dbUser);
                        rb.getProps().remove("decrypt");
                        rb.getProps().put("password", dbPass);
                        String url = rb.getProps().get("jdbcUrl");
                        String bizDb = StringUtils.substringBefore(StringUtils.substringAfterLast(url, "/"), "?");
                        rb.getProps().put("jdbcUrl", String.format("jdbc:mysql://%s:%s/%s?useUnicode=true&characterEncoding=UTF-8", properties.get("address"), properties.get("port"), bizDb));
                        pstmt.close();

                        stmt = conn.createStatement();
                        applicationListener.publish(I18N.getLabel("setup.biz.create", bizDb), "", 50);
                        stmt.execute("DROP DATABASE IF EXISTS `" + bizDb + "`");
                        stmt.execute("CREATE DATABASE `" + bizDb + "` CHARACTER SET 'utf8' COLLATE 'utf8_bin'");
                        stmt.close();

                        conn.setCatalog(bizDb);//切换到业务数据库
                        applicationListener.publish(I18N.getLabel("setup.biz.import", bizDb), "", 60);

                        data = IOUtils.readLines(is = new FileInputStream(System.getProperty("easyplatform.home") + "/scheme/app_biz.sql"), "utf-8");
                        is.close();
                        stmt = conn.createStatement();
                        sb.setLength(0);
                        for (String line : data) {
                            if (!line.startsWith("--") && !Strings.isBlank(line)) {
                                sb.append(line);
                                if (line.endsWith(";")) {
                                    String sql = sb.toString();
                                    if (log.isDebugEnabled())
                                        log.debug(sql);
                                    applicationListener.publish(null, String.format("%s", StringUtils.abbreviate(sql, 100)), 70);
                                    stmt.execute(sql);
                                    sb.setLength(0);
                                }
                            }
                        }
                        sb.setLength(0);
                        TransformerFactory.newInstance().transformToXml(rb, new StringOutputStream(sb));
                        conn.setCatalog(dbName);//切换到元数据库
                        pstmt = conn.prepareStatement("UPDATE ep_model_info SET content=? WHERE modelId=?");
                        pstmt.setString(1, sb.toString());
                        pstmt.setString(2, e.getId());
                        pstmt.executeUpdate();
                        Ini.Section section = engineConfiguration.getAttributes("entities");
                        section.put("username", dbUser);
                        section.put("password", dbPass);
                        section.remove("decrypt");
                        section.put("jdbcUrl", String.format("jdbc:mysql://%s:%s/%s?useUnicode=true&characterEncoding=UTF-8", properties.get("address"), properties.get("port"), dbName));
                        data = IOUtils.readLines(is = new FileInputStream(System.getProperty("easyplatform.home") + "/config/easyplatform.conf"), "utf-8");
                        List<String> newConfig = new ArrayList<>(data.size() - 1);
                        for (String line : data) {
                            if (line.trim().startsWith("username")) {
                                newConfig.add(String.format("username = %s", dbUser));
                            } else if (line.trim().startsWith("password")) {
                                newConfig.add(String.format("password = %s", dbPass));
                            } else if (line.trim().startsWith("jdbcUrl")) {
                                newConfig.add(String.format("jdbcUrl = %s", section.get("jdbcUrl")));
                            } else if (!line.trim().startsWith("decrypt"))
                                newConfig.add(line);
                        }
                        FileOutputStream fos = new FileOutputStream(System.getProperty("easyplatform.home") + "/config/easyplatform.conf");
                        IOUtils.writeLines(newConfig, null, fos, "utf-8");
                        fos.close();
                        applicationListener.publish(null, I18N.getLabel("setup.success"), 100);
                    } else {
                        applicationListener.publish("", I18N.getLabel("setup.config.error"), 50);
                    }
                } catch (Exception e) {
                    if (log.isErrorEnabled())
                        log.error("setupModel", e);
                    applicationListener.publish("", I18N.getLabel("setup.fail", e.getMessage()), 0);
                } finally {
                    IOUtils.closeQuietly(is);
                    DaoUtils.closeQuietly(stmt, rs);
                    DaoUtils.closeQuietly(pstmt);
                    DaoUtils.closeQuietly(conn);
                }
            }
        }).start();
    }

    @Override
    public void start() {
        try {
            startTime = new Date();
            if (log.isInfoEnabled())
                log.info(I18N.getLabel("easyplatform.starting"));
            engineConfiguration.start();
            Runtime.getRuntime().addShutdownHook(new Thread() {
                public void run() {
                    if (log.isInfoEnabled())
                        log.info("Execute shutdownHook.");
                    EngineImpl.this.stop();
                }
            });
            if (log.isInfoEnabled())
                log.info(I18N.getLabel("easyplatform.started", new SimpleDateFormat(
                                "yyyy-MM-dd HH:mm:ss").format(startTime),
                        System.currentTimeMillis() - startTime.getTime()));
        } finally {
            Contexts.clear();
        }
    }

    @Override
    public void stop() {
        if (log.isInfoEnabled())
            log.info(I18N.getLabel("easyplatform.stopping"));
        ApplicationService sched = engineConfiguration.getService(SystemServiceId.SYS_SCHEDULER);
        Iterator<ApplicationService> itr = engineConfiguration.getServices().iterator();
        while (itr.hasNext()) {
            ApplicationService service = itr.next();
            if (service.getState() != StateType.STOP && service != sched)
                service.stop();
        }
        if (sched != null)
            sched.stop();
        if (engineConfiguration.getApplicationListener() != null)
            engineConfiguration.getApplicationListener().close();
        if (engineConfiguration.getKernelThreadPoolExecutor() != null)
            engineConfiguration.getKernelThreadPoolExecutor().shutdown();
        if (log.isInfoEnabled()) {
            long serviceTimes = System.currentTimeMillis()
                    - startTime.getTime();
            serviceTimes = serviceTimes / (1000 * 60 * 60);
            log.info(I18N.getLabel("easyplatform.stopped", new SimpleDateFormat(
                    "yyyy-MM-dd HH:mm:ss").format(new Date()), serviceTimes));
        }
        startTime = null;
    }

    @Override
    public <T> T getEngineService(Class<?> clazz) {
        return engineConfiguration.getEngineService(clazz);
    }

    @Override
    public ApplicationService getProjectService(String serviceId) {
        return engineConfiguration.getProjectService(serviceId);
    }

    @Override
    public Set<Map.Entry<Class<?>, Object>> getEngineServices() {
        return engineConfiguration.getEngineServices();
    }

    @Override
    public boolean isSessionKeepAlive() {
        return engineConfiguration.isSessionKeepAlive();
    }

    @Override
    public int getSessionTimeout() {
        return engineConfiguration.getSessionTimeout();
    }

    @Override
    public void setApplicationListener(ApplicationListener listener) {
        engineConfiguration.setApplicationListener(listener);
    }

    @SuppressWarnings("unchecked")
    @Override
    public Collection<IProjectService> getProjectServices() {
        return engineConfiguration.getProjectServices();
    }

    @Override
    public EngineConfiguration getEngineConfiguration() {
        return engineConfiguration;
    }

    @Override
    public String getConfig(String name) {
        return engineConfiguration.getPlatformAttribute(name);
    }
}
